package pt.ua.miect.sd.simuladorpesca.threads;

import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import org.apache.log4j.Logger;
import pt.ua.miect.sd.simuladorpesca.SimulationConfig;
import pt.ua.miect.sd.simuladorpesca.entities.Boat;
import pt.ua.miect.sd.simuladorpesca.entities.FishNet;
import pt.ua.miect.sd.simuladorpesca.entities.FleetMessageBox;
import pt.ua.miect.sd.simuladorpesca.entities.Map;
import pt.ua.miect.sd.simuladorpesca.entities.MapEntity;
import pt.ua.miect.sd.simuladorpesca.entities.MapPosition;
import pt.ua.miect.sd.simuladorpesca.entities.RadioMessage;

public class BoatWorker implements Runnable, ISimWorker {

    private CyclicBarrier tickBarrier;
    private Boat boat;
    private Thread t;

    public BoatWorker(CyclicBarrier tbarrier, Boat boat) {
        this.tickBarrier = tbarrier;
        this.boat = boat;
        Map.getInstance().addEntity(boat.getId(), boat);
        t = new Thread(this);
        //Simulator.registerWorker(this);
    }

    public void start() {
        t.start();
    }

    @Override
    public void run() {
        boolean done = false;
        MapPosition portPosition = Map.getInstance().getPort().getMapPosition();
        MapPosition sonarPing = null;
        MapPosition destination = null; //used to define meeting point
        MapEntity sender = null;
        Boat catchMate = null;
        int waitcountdown = SimulationConfig.MAX_WAIT;
        while (!done) {
            try {
                tickBarrier.await();
                boolean hasMessages = true;
                do {
                    RadioMessage msg = FleetMessageBox.getInstance().getRadioMessage(boat.getId());
                    if (msg == null) {
                        hasMessages = false;
                    } else {
                        switch (msg.getMessage()) {
                            case SeasonStart:
                                boat.setStatus(Boat.BoatStatus.SearchingForFish);
                                Logger.getRootLogger().info(boat.getId() + " has received orders from " + msg.getSenderID() + " to begin the fishing Season! Here we go!!");
                                break;
                            case EndOfSeason:
                                if (boat.getStatus() != Boat.BoatStatus.AtPort) {
                                    if (boat.getStatus() == Boat.BoatStatus.EnclosingTheSchool) {
                                        retrieveNet(catchMate);
                                    }
                                    boat.setStatus(Boat.BoatStatus.ReturningToPort);
                                    Logger.getRootLogger().info(boat.getId() + " has been informed that the season is over!!");

                                }
                                break;
                            case Acknowledge:
                                Logger.getRootLogger().info(boat.getId() + " receives an Acknowledge from " + msg.getSenderID() + "!!");
                                if (boat.getStatus() == Boat.BoatStatus.WaitingForCompanion) {
                                    sender = Map.getInstance().getEntity(msg.getSenderID());
                                    if (sender instanceof Boat) {
                                        catchMate = (Boat) sender;
                                        Logger.getRootLogger().info(boat.getId() + " receives confirmation that " + catchMate.getId() + " will join the Catch!");
                                        boat.setStatus(Boat.BoatStatus.TrackingSchool);
                                    }
                                } else if (boat.getStatus() == Boat.BoatStatus.EnclosingTheSchool) {
                                    retrieveNet(catchMate);

                                }
                                break;
                            case JoinCatch:
                                if (boat.getStatus() != Boat.BoatStatus.SearchingForFish) {
                                    //invalid request
                                    FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), msg.getSenderID(), RadioMessage.MessageType.Abort));
                                } else {
                                    sender = Map.getInstance().getEntity(msg.getSenderID());
                                    if (sender instanceof Boat) {
                                        catchMate = (Boat) sender;
                                        Logger.getRootLogger().info(boat.getId() + " receives a request to join " + catchMate.getId() + " in a Catch at " + msg.getPosition() + "!");
                                        boat.setStatus(Boat.BoatStatus.JoiningCompanion);
                                        destination = msg.getPosition();
                                    }
                                }
                                break;
                            case Abort:
                                switch (boat.getStatus()) {
                                    case JoiningCompanion:
                                    case TrackingSchool:
                                    case WaitingForCompanion:
                                        Logger.getRootLogger().info(boat.getId() + " aborting!");
                                        boat.setStatus(Boat.BoatStatus.SearchingForFish);
                                        destination = null;
                                        break;
                                    default:
                                        Logger.getRootLogger().info(boat.getId() + " receives an abort message from " + msg.getSenderID() + " but will ignore it!");
                                }
                                break;


                            case CastNet:
                                sender = Map.getInstance().getEntity(msg.getSenderID());
                                if (sender instanceof Boat && ((Boat) sender).equals(catchMate)) {
                                    if (boat.getStatus() == Boat.BoatStatus.TrackingSchool) {
                                        if (boat.getMapPosition().equals(msg.getPosition())) {
                                            Map.getInstance().castFishNet(boat, boat.getMapPosition());
                                            boat.setStatus(Boat.BoatStatus.EnclosingTheSchool);
                                            Logger.getRootLogger().info(boat.getId() + " has cast his Fishing Net at " + boat.getMapPosition());
                                            FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.RetriveNet));
                                        } else {
                                            FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Abort));
                                        }
                                    }

                                }
                                break;

                            case RetriveNet:
                                sender = Map.getInstance().getEntity(msg.getSenderID());
                                if (sender instanceof Boat && ((Boat) sender).equals(catchMate) && boat.getStatus() == Boat.BoatStatus.EnclosingTheSchool) {
                                    retrieveNet(catchMate);
                                }
                                break;

                            case FishAt:
                                sender = Map.getInstance().getEntity(msg.getSenderID());
                                if (sender instanceof Boat && ((Boat) sender).equals(catchMate) && boat.getStatus() == Boat.BoatStatus.TrackingSchool) {
                                    destination = msg.getPosition();
                                }
                                break;


                        }
                    }
                } while (hasMessages);


                switch (boat.getStatus()) {

                    case AtPort:
                        //does nothing... waits for the next season
                        break;

                    case StoringCatch:
                        if (boat.getFishcount() >= SimulationConfig.MAX_BOAT_CAPACITY) {
                            FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Abort));
                            boat.setStatus(Boat.BoatStatus.ReturningToPort);
                        }
                        break;

                    case ReturningToPort:
                        if (boat.getMapPosition().equals(portPosition)) {
                            boat.setStatus(Boat.BoatStatus.AtPort);
                            Logger.getRootLogger().info(boat.getId() + " arrived at the Port! This season they catched " + boat.getFishcount() + " fish!");
                            FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), boat.getCompanyId(), RadioMessage.MessageType.AtHome));
                        } else {
                            moveBoatTowards(portPosition);

                        }
                        break;
                    case SearchingForFish:
                        List<MapPosition> moves = MapPosition.getValidMovements(boat.getMapPosition());
                        for (MapPosition mapPosition : moves) {
                            if (mapPosition != null && Map.getInstance().allowMove(boat, mapPosition)) {
                                //moved
                                Logger.getRootLogger().info(boat.getId() + " has moved to position " + boat.getMapPosition());
                                break;
                            }
                        }
                        sonarPing = Map.getInstance().sonar(boat.getMapPosition());
                        if (sonarPing != null) {
                            Logger.getRootLogger().info(boat.getId() + " has a Fish School on his Sonar and will request help!");
                            FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), boat.getCompanyId(), RadioMessage.MessageType.FoundFish, sonarPing));
                            boat.setStatus(Boat.BoatStatus.WaitingForCompanion);
                            catchMate = null;
                        }
                        break;
                    case TrackingSchool:
                        if (destination != null && !destination.equals(boat.getMapPosition())) {
                            moveBoatTowards(destination);
                        } else {
                            MapPosition newPing = Map.getInstance().sonar(boat.getMapPosition(), sonarPing);
                            if (newPing == null) {
                                FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Abort));
                                Logger.getRootLogger().info(boat.getId() + " has lost contact with Fish School");
                                boat.setStatus(Boat.BoatStatus.SearchingForFish);
                            } else if (newPing.equals(boat.getMapPosition())) {
                                castFishNet(catchMate);
                            } else if (!newPing.equals(sonarPing)) {
                                FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.FishAt, newPing));
                                sonarPing = newPing;
                                Logger.getRootLogger().info(boat.getId() + "'s sonar reports that the fish school moves now to " + newPing);
                            }
                        }

                        break;
                    case JoiningCompanion:
                        if (destination != null) {
                            if (destination.equals(boat.getMapPosition())) {
                                boat.setStatus(Boat.BoatStatus.TrackingSchool);
                                FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Acknowledge));
                                Logger.getRootLogger().info(boat.getId() + " has arrived at " + boat.getMapPosition() + " to help " + catchMate.getId() + " to catch some Fish!");
                            } else {
                                moveBoatTowards(destination);
                            }
                        }

                        break;

                    case WaitingForCompanion:
                        //does nothing just waits
                        waitcountdown--;
                        if (waitcountdown > 0) {
                            Logger.getRootLogger().info(boat.getId() + " is waiting  at " + boat.getMapPosition());
                        } else {
                            if (catchMate != null) {
                                FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Abort));
                            }
                            boat.setStatus(Boat.BoatStatus.SearchingForFish);
                            Logger.getRootLogger().info(boat.getId() + " will wait no longer and will search some Fish!");
                        }
                        break;

                    case EnclosingTheSchool:
                        //waits for the message to retrieve the net
                        Logger.getRootLogger().info(boat.getId() + " is busy with " + catchMate.getId() + " Enclosing a School at " + boat.getMapPosition());
                        break;

                }

            } catch (InterruptedException ex) {
                Logger.getLogger(BoatWorker.class).error(ex);
            } catch (BrokenBarrierException ex) {
                Logger.getLogger(BoatWorker.class).error(ex);
                done = true;
            }


        }


    }

    /**
     * gets the net back
     * @param catchMate
     */
    private void retrieveNet(Boat catchMate) {
        FishNet net = Map.getInstance().retrieveNet(boat, boat.getMapPosition());
        if (net != null) {
            Logger.getRootLogger().info(boat.getId() + " retrives the Fishing Net containing " + net.getFishCount() + " Fish");
            boat.setStatus(Boat.BoatStatus.StoringCatch);
            if (net.getFishCount() > 0) {
                long totalFish = net.getFishCount() + boat.getFishcount();
                if (totalFish > SimulationConfig.MAX_BOAT_CAPACITY) {
                    Logger.getRootLogger().info(boat.getId() + " reports that " + (totalFish - SimulationConfig.MAX_BOAT_CAPACITY) + " Fish had to be discarded because the boat is full!");
                    boat.setFishcount(SimulationConfig.MAX_BOAT_CAPACITY);
                } else {
                    boat.setFishcount(totalFish);
                }

                FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.Acknowledge));
            }
        }
    }

    private void moveBoatTowards(MapPosition to) {
        List<MapPosition> routes = MapPosition.getRoutesTo(boat.getMapPosition(), to, true);
        for (MapPosition route : routes) {
            if (route != null) {
                boolean moved = Map.getInstance().allowMove(boat, route);
                if (moved) {
                    Logger.getRootLogger().info(boat.getId() + " has moved to position " + boat.getMapPosition());
                    break;
                }
            }
        }
    }

    private void castFishNet(Boat catchMate) {
        FleetMessageBox.getInstance().setRadioMessage(new RadioMessage(boat.getId(), catchMate.getId(), RadioMessage.MessageType.CastNet, boat.getMapPosition()));
        Map.getInstance().castFishNet(boat, boat.getMapPosition());
        Logger.getRootLogger().info(boat.getId() + " has cast his Fishing Net at " + boat.getMapPosition());
        boat.setStatus(Boat.BoatStatus.EnclosingTheSchool);
    }
}
